package com.liuqi.loggers.core.util;

import com.liuqi.loggers.core.Converter;
import com.liuqi.loggers.core.converters.ClassConverter;
import com.liuqi.loggers.core.converters.DateConverter;
import com.liuqi.loggers.core.converters.LevelConverter;
import com.liuqi.loggers.core.converters.LiteralConverter;
import com.liuqi.loggers.core.converters.MessageConverter;
import com.liuqi.loggers.core.converters.ThreadConverter;
import com.liuqi.loggers.core.converters.ThrowableConverter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 格式化日志输出文本解析为日志输出转换器
 *
 * @author liuqi
 * @date 2024/11/5 16:58
 **/
public class ConverterParser {
    enum State {
        LITERAL_STATE, KEYWORD_STATE
    }

    private static final Map<String, Class<? extends Converter>> convertMaps;
    static {
        convertMaps = new HashMap<String, Class<? extends Converter>>();
        convertMaps.put("date", DateConverter.class);
        convertMaps.put("level", LevelConverter.class);
        convertMaps.put("thread", ThreadConverter.class);
        convertMaps.put("class", ClassConverter.class);
        convertMaps.put("message", MessageConverter.class);
        convertMaps.put("throwable", ThrowableConverter.class);
    }

    public static void addConvert(String name, Class<? extends Converter> classes) {
        convertMaps.put(name, classes);
    }

    private static final char PERCENT_CHAR = '%';

    private List<Converter> converters = new ArrayList<Converter>();

    private final String pattern;
    private final int patternLength;
    private int pointer = 0;
    private State state = State.LITERAL_STATE;

    public ConverterParser(String pattern) {
        this.pattern = pattern;
        this.patternLength = pattern.length();
    }

    public List<Converter> parse() {
        // %date %level %thread %class : %msg %throwable
        StringBuilder buf = new StringBuilder();
        while (pointer < patternLength) {
            char c = pattern.charAt(pointer);
            pointer++;
            switch (state) {
                case LITERAL_STATE:
                    handleLiteralState(c, buf);
                    break;
                case KEYWORD_STATE:
                    handleKeywordState(c, buf);
                    break;
            }
        }
        switch (state) {
            case LITERAL_STATE:
                addConverter(buf, LiteralConverter.class);
                break;
            case KEYWORD_STATE:
                addConverterWithKeyword(buf);
                break;
        }

        return converters;
    }

    private void handleLiteralState(char c, StringBuilder buf) {
        switch (c) {
            case PERCENT_CHAR:
                addConverter(buf, LiteralConverter.class);
                state = State.KEYWORD_STATE;
                break;
            default:
                buf.append(c);
        }
    }

    private void handleKeywordState(char c, StringBuilder buf) {
        if (Character.isJavaIdentifierPart(c)) {
            buf.append(c);
        } else {
            addConverterWithKeyword(buf);
            state = State.LITERAL_STATE;
            buf.append(c);
        }
    }

    private void addConverterWithKeyword(StringBuilder buf) {
        String keyword = buf.toString();
        if (convertMaps.containsKey(keyword)) {
            addConverter(buf, convertMaps.get(keyword));
        } else {
            buf.insert(0, PERCENT_CHAR);
            addConverter(buf, LiteralConverter.class);
        }
    }

    private void addConverter(StringBuilder buf, Class<? extends Converter> aClass) {
        if (buf.length() > 0) {
            String result = buf.toString();
            if (LiteralConverter.class.equals(aClass)) {
                converters.add(new LiteralConverter(result));
            } else {
                try {
                    converters.add(aClass.newInstance());
                } catch (Exception e) {
                    throw new IllegalStateException("Create Converter error. Class: " + aClass, e);
                }
            }
            buf.setLength(0);
        }
    }

}
